#[derive(Debug)]
struct Item(i32);

fn sort(items: &mut Vec<Item>) {
    items.sort_by_key(|item| item.0)
}

// 这里定义FnOnce使传入的闭包更兼容，因为FnOnce是FnMut和Fn的超集，实现FnMut必须先实现FnOnce，实现Fn必须先实现FnMut
fn calculator<F: FnOnce(i32, i32) -> i32>(func: F, a: i32, b: i32) -> i32 {
    func(a, b)
}

fn wrap<F: FnOnce(T, T) -> T, T>(func: F, a: T, b: T) {
    func(a, b);
}

fn main() {
    // 闭包，匿名函数，可以将闭包赋值给一个变量或者作为参数传递，闭包可以捕获其环境中的变量 gc

    // 普通闭包
    let f = |a, b| a + b;
    let result = f(1, 2);
    println!("1 + 2 = {}", result);
    // 以下不能被编译，原因：闭包在调用过后就锁定了类型，后面调用都必须按照第一次的类型进行
    // let result = f(1.1, 2.2);

    // 回调闭包
    let mut items = vec![Item(1), Item(22), Item(15), Item(25), Item(83)];
    println!("{:?}", items);
    sort(&mut items);
    println!("{:?}", items);

    // 分配Fn类型，取决于闭包对外部变量的使用方式，分为Fn、FnMut和FnOnce，分别对应不可变引用、可变引用和所有权转移
    // 获取不可变引用参数，会将闭包编译为Fn类型，闭包可调用多次
    let s = String::from("rust");
    let f = || {
        println!("{s}");
    };
    f();
    f();
    println!("{s}");

    // 获取可变引用参数，会将闭包编译为FnMut类型，闭包可调用多次
    let mut s = String::from("rust");
    let mut f = || {
        s = String::from("RUST");
    };
    f();
    f();
    println!("{s}");

    // 获取所有值转移，会将闭包编译为FnOnce类型，闭包只允许被调用一次
    let s = String::from("rust");
    let f = || {
        // drop操作使s变量值在内存中销毁，所有这里再编译时被指定FnOnce
        // std::mem::drop(s);

        // 或者将ss所有权转移，也会被指定为FnOnce
        s
    };
    f();
    // f();
    // println!("{s}");

    // 强制分配类型
    // 用move关键字（仅对非标量类型有效，也就是分配在堆上的数据），将闭包里的参数被强制转移，并且是在定义时就已经被转移了，无论方法闭包调不调用，后面都无法使用闭包里的参数了
    let i = 10;
    let f = move || {
        println!("{i}");
    };
    println!("{i}"); // compile ok
    f();
    println!("{i}"); // compile ok

    let s = String::from("rust");
    let f = move || {
        println!("{s}");
    };
    println!("{s}"); // compile bad
    f();
    println!("{s}"); // compile bad

    // 闭包作为参数传递和返回
    let x = 10;
    let add = |a, b| a + b + x;
    let result = calculator(add, 1, 8);
    println!("result: {}", result);

    let mut y = 6;
    let mul = |a, b| {
        y *= 2;
        a * b * y
    };
    let result = calculator(mul, 1, 3);
    println!("result: {}", result);

    let z = 0;
    let div = move |a, b| a / b + z;
    let result = calculator(div, 10, 2);
    println!("result: {}", result);
    // 疑问：这里变量z为何还能被使用，已经有了move声明，并且z是在闭包里
    // 解答：因为z是分配在栈上的标量数据（拥有Copy特质），在赋值或者是给函数传值过程中会直接复制一份，而原来变量不受影响
    println!("{z}");

    // 以下求证，上述疑问的猜想，做三组对照验证，分别整型、字符串和字符串切片
    let a = 1;
    let f = |value| println!("{}", value);
    f(a);
    println!("{}", a);

    let b = "rust";
    let f = |value| println!("{}", value);
    f(b);
    println!("{}", b);

    let c = String::from("rust");
    let f = |value| println!("{}", value);
    f(c);
    // 结论：实现Copy特质的类型在当参数传入时，所有权是不会发生转移的
    // println!("{}", c);

    // 将闭包做返回值，直接返回

    // 以下场景说两点（常规，没if判断）
    //   1.Fn是特质，但编译器期待返回是类型（impl Fn是隐式类型）
    //   2.num参数的所有权转移问题，闭包里使用了num变量，闭包被返回后，num变量就会被销毁
    fn factory_1() -> impl Fn(i32) -> i32 {
        let num = 10;
        move |x| x + num
    }

    let f = factory_1();

    let r = f(1);

    println!("r: {r}");

    // 以下场景说两点（复杂，带if判断），闭包返回值类型是动态的，需要用Box::new包裹
    fn factory_2(x: i32) -> Box<dyn Fn(i32) -> i32> {
        let num = 10;

        if x > 0 {
            Box::new(move |x| x + num)
        } else {
            Box::new(move |x| x - num)
        }
    }
}
